Do's and Don'ts
https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html
<一般的な型について>
Number、String、Boolean、Symbol、Objectを使うべきではない
使うならnumber、string、boolean、symbol、objectを使うべき。
https://scrapbox.io/files/67ad49c8fc2f49fb3236c30b.png
(参考) https://typescriptbook.jp/reference/values-types-variables/boxing#ラッパーオブジェクトとtypescriptの型
以下のように値だけ満たすものであれば代入できてしまうので使うメリットなし。
code:ts
const boolLike = {
valueOf(): boolean {
return true;
},
};
const bool1: Boolean = boolLike;
const bool2: Boolean = true;
const bool3: boolean = true;
const bool4: boolean = boolLike; // エラー
未使用の型パラメータを使用しているジェネリクスは使用すべきではない
型パラメータ(T)を省略するのは無意味。
code:ts
interface Box<T> {
contents: string; // T が使われていない
}
const numBox: Box<number> = { contents: "Hello" }; // ✅ これが通ってしまう
const strBox: Box<boolean> = { contents: "World" }; // ✅ これも通る
(参考) https://github.com/Microsoft/TypeScript/wiki/FAQ#why-is-astring-assignable-to-anumber-for-interface-at--
anyをどうしても使うなら、unknown
any型は誤用されがちで、どんな種類のデータでも許容してしまうため、TypeScriptの目的である型安全性が損なわれてしまう。 その代わりに、型が不確かな場合はunknownを選択する。 anyとは異なり、unknownは操作を行う前に型チェックを必要とするため、安全性が確保される。
unknownの場合、型ガードを一緒に使うのがベター。
※使うなら、APIレスポンスなど動的な場合
code:ts
// any
function processInput(input: any) {
console.log(input.toUpperCase()); // inputがstring以外でもエラーにならない (コンパイル時)
}
// unknown
function processInput(input: unknown) {
console.log(input.toUpperCase()); // 型エラー: Object is of type 'unknown'.
}
// unkownの実用例
function processInput(input: unknown) {
if (typeof input === "string") {
console.log(input.toUpperCase()); // ✅ 型チェック後ならOK
} else {
console.log("Not a string");
}
}
processInput("hello"); // ✅ "HELLO"
processInput(123); // ✅ "Not a string" (実行時エラーなし)
(参考) https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#any
<コールバックについて>
コールバックの使わない戻り値は、anyを使わずにvoidを使う
anyだと戻り値など無関係に素通りさせてしまうので、関数の中で型安全性が崩れる可能性がある。
コンパイル時点でエラーがcallbackの戻り値がないということを検知できるので、実行時エラーを未然に防げる。
例) callbackが戻り値を渡さない想定で関数を作っているケース
code:ts
type Callback = (data: string) => void; // voidではなくanyを使うと、processDataのconsole.logでは素通りする
function processData(callback: Callback) {
const result = callback("Hello"); // result の型は void
console.log(result.toUpperCase()); // ❌ 後から追加した場合、コンパイルエラー!
}
processData((message) => {
console.log(message);
return 42; // return は無視される
});
Optional Parameters in Callbacks
コールバックの引数の型はオプショナルにすべきではない。
(理由としては、通常の関数ならOK)
呼び出す側が引数をどのように入れるかに依存し、不確実となり扱いが難しくなるため。
code:ts
// NG
interface Fetcher {
getObject(done: (data: unknown, elapsedTime?: number) => void): void;
}
// OK
interface Fetcher {
getObject(done: (data: unknown, elapsedTime: number) => void): void;
}
以下の例では、実行時して初めてエラーに気づくのでよろしくない。
コールバック実装者と元関数定義者で意図が異なる。
code:ts
// 関数実装者
interface DeliveryService {
deliverPackage(onDelivered: (trackingId: string, deliveryTime?: number) => void): void;
}
const delivery: DeliveryService = {
deliverPackage(onDelivered) {
onDelivered("ABC123", 42); // ✅ 配達時間あり
onDelivered("XYZ789"); // ⚠️ 配達時間なし(deliveryTime が undefined になる)
},
};
// コールバック実装を渡す
delivery.deliverPackage((trackingId, deliveryTime) => {
console.log(荷物 ${trackingId} が届きました!);
// ❌ コールバックの実装者は "deliveryTime は必ずある" と思い込んでいる
console.log(配達時間: ${deliveryTime.toFixed(2)} 分); // ⚠️ もし undefined なら実行時エラー!
});
(参考) https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#optional-parameters-in-callbacks
~~~~~~~~~ここからオーバーロードの話なので、一旦別にいく~~~~~~~~~
関数のオーバーロードについて
Overloads and Callbacks
コールバックの型が異なる場合は、オーバーロードを使わずに1定義で完結させる。
code:ts
/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;
// 最初の関数の型にマッチしてしまう
beforeAll((done) => {
done(); // ❌ TypeScript は done が undefined だと誤解する可能性がある
});
/* OK */
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;
オーバーロードの順番
typescriptは、最初に一致した型を適用する特性があるので最初になんでも適用される型を適用すべきではない。
関数のオーバーロードは、具体性のある型定義を先に記述し、後から一般的な型定義をオーバーロードすべき。
code:ts
/* WRONG */
declare function fn(x: unknown): unknown;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: unknown, wat?
/* OK */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: unknown): unknown;
var myElem: HTMLDivElement;
var x = fn(myElem); // x: string, :)
Use Optional Parameters
可能な限りオプションのパラメータを使用すべき。
冗長となるので、オプショナルパラメータを使えるなら使うことで、シンプルかつ、意図しない挙動が起きる可能性を防ぐ。
以下の例では、同じ関数をオーバーロードしており、引数の数が異なる。
code:ts
/* WRONG */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}
/* OK */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}
ただし、以下の場合は、オーバーロードが適している。
code:ts
interface Example {
diff(one: string, two: string): number;
diff(one: number, two: number): number;
}
Use Union Type
引数の型だけが異なるものなら、オーバーロードを使わずユニオン型を使うべき。
以下の例では、同じ関数でも引数がnumberとstringのものがあるので、ユニオンにできる。
code:ts
/* WRONG */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
/* OK */
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment; // ユニオン型
}